Chapter 1: Functional vs Imperative
명령형 스타일로 된 코드를 함수형 스타일로 바꾸어 보기
const products = [
{ id: 'candy', name: 'Candy', price: 10, onSale: true },
{ id: 'ice-cream', name: 'Ice cream', price: 20, onSale: true },
{ id: 'cake', name: 'Cake', price: 30, onSale: false },
{ id: 'donuts', name: 'Donuts', price: 12, onSale: true },
{ id: 'chocolate', name: 'Chocolate', price: 15, onSale: false },
{ id: 'flower', name: 'Flower', price: 40, onSale: false },
{ id: 'sofa', name: 'Sofa', price: 120, onSale: true },
{ id: 'bed', name: 'Bed', price: 400, onSale: true },
];
1. Imperative
가격이 30 이하인 제품 중에서 3개를 뽑고 그 가격들의 총합을 구하기.
// Imperative
function imperative() {
let total = 0;
let count = 0;
for (const a of products) {
if (a.price < 30) {
total += a.price;
count++;
}
if (count === 3) break;
}
console.log(total);
}
2. Functional
const isIterable = (iter) => typeof iter[Symbol.iterator] === 'function';
const curry =
(f) =>
(a, ...args) =>
args.length ? f(a, ...args) : (...args2) => f(a, ...args2);
const map = curry(function* (f, iter) {
for (const a of iter) {
yield f(a);
}
});
const filter = curry(function* (f, iter) {
for (const a of iter) {
if (f(a)) yield a;
}
});
const take = curry((length, iter) => {
const res = [];
for (const a of iter) {
res.push(a);
if (res.length === length) return res;
}
});
const reduce = curry((f, acc, iter) => {
if (!iter && isIterable(acc)) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
const add = curry((a, b) => a + b);
const pipe = (...args) => reduce((a, f) => f(a), args);
// Functional
pipe(
products,
map((a) => a.price),
filter((p) => p < 30),
take(3),
reduce(add),
console.log,
);
3. Generator 와 Lazy Evaluation(지연평가)
pipe(
products,
map((a) => a.price), // Lazy
filter((p) => p < 30), // Lazy
take(3),
reduce(add),
console.log,
);
해당 코드에서 map, filter는 Generator 를 사용하였기 때문에 take가 3개를 만족하게 되면 더이상 실행되지 않는다.
4. 함수형의 장점
- 가독성이 좋음
- 재사용성이 좋음
- 수정이 용이함